import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Iterator;

public class SampleAndHold extends DrawObject
{
    // DrawObject abilities:
    @Override
    public boolean IsSampleAndHold()
    {
        return true;
    }

    @Override
    public boolean CanBeDeletedByUser()
    {
        return true;
    }

    @Override
    public boolean CanBeExcitedByUser()
    {
        return true;
    }

    @Override
    public boolean CanBeMovedByUser()
    {
        return true;
    }

    @Override
    public boolean CanBeSelectedByUser()
    {
        return true;
    }
    // end of DrawObject abilities

    private ArrayList<SampleAndHoldInputNode> InputNodes = new ArrayList<SampleAndHoldInputNode>();
    private ArrayList<SampleAndHoldOutputNode> OutputNodes = new ArrayList<SampleAndHoldOutputNode>();

    private final int StrengthMax = 78;
    private int ExcitementTicksRemaining = 0;

    public SampleAndHold(Point Pos)
    {
        SetPos(Pos);
        SetSize(new Point(100, 50));
        InitializeExcitement(ExcitementOnceMilliseconds);

        // simulate TWO TextField activations although there was only one:
        SetStrength(Simulation.TicksPerIOTextAreaLine());
    }

    @Override
    protected void AddExcitementForNextTick(double ExcitementAdd)
    {
        // uses the ExciteAsPartner() excitement donation scheme!
    }

    public SampleAndHoldInputNode AddInputNode()
    {
        SampleAndHoldInputNode SampleAndHoldInputNodeNew = new SampleAndHoldInputNode(new Point(Pos.x - 10, Pos.y + InputNodes.size() * 20));

        SampleAndHoldInputNodeNew.SetCompoundId(((SampleAndHold) this).CompoundId);
        SampleAndHoldInputNodeNew.ExcitementPartner = (SampleAndHold) this;

        InputNodes.add(SampleAndHoldInputNodeNew);

        return SampleAndHoldInputNodeNew;
    }

    public void AddInputNode(SampleAndHoldInputNode AddNode)
    {
        AddNode.ExcitementPartner = (SampleAndHold) this;

        InputNodes.add(AddNode);
    }

    public SampleAndHoldOutputNode AddOutputNode()
    {
        SampleAndHoldOutputNode SampleAndHoldOutputNodeNew = new SampleAndHoldOutputNode(new Point(Pos.x + 100, Pos.y + Size.y / 2));

        SampleAndHoldOutputNodeNew.SetCompoundId(((SampleAndHold) this).CompoundId);

        ((SampleAndHold) this).ExcitementPartner = SampleAndHoldOutputNodeNew;

        OutputNodes.add(SampleAndHoldOutputNodeNew);

        return SampleAndHoldOutputNodeNew;
    }

    public void AddOutputNode(SampleAndHoldOutputNode AddNode)
    {
        ((SampleAndHold) this).ExcitementPartner = AddNode;

        OutputNodes.add(AddNode);
    }

    @Override
    protected void AfterCollectingAllExcitementForNextTick()
    {
        // uses the ExciteAsPartner() excitement donation scheme!
    }

    @Override
    public void Draw(Graphics g)
    {
        if (!(IsWithinWindowBounds(this, 0)))
            return;

        // background (overdraw grid)
        if (IsHighlighted || IsPartOfMultiSelection)
            SetColor(g, Color.GREEN);
        else
            SetColor(g, Color.LIGHT_GRAY);
        FillRect(g, Pos.x, Pos.y, Size.x, Size.y);

        // strength bar
        SetColor(g, Color.YELLOW);
        FillRect(g, Pos.x, Pos.y, 1 + Strength * 80 / StrengthMax, 20);

        // strength bar frame
        if (IsHighlighted || IsPartOfMultiSelection)
            SetColor(g, Color.BLACK);
        else
            SetColor(g, GetExcitementColor());
        DrawRect(g, Pos.x, Pos.y, 80, 20);

        // frame
        if (IsHighlighted || IsPartOfMultiSelection)
            SetColor(g, Color.BLACK);
        else
            SetColor(g, GetExcitementColor());
        DrawRect(g, Pos.x, Pos.y, Size.x, Size.y);

        // strength value text
        SetColor(g, Color.BLACK);
        g.setFont(GetSmallFont());
        DrawString(g, ((Integer) Strength).toString(), Pos.x + 80 + 3, Pos.y + 15);

        // info text
        DrawString(
            g, "sim. " + (int) (Strength / Simulation.TicksPerIOTextAreaLine()) + " add.", // ATTENTION: keep in mind Lines could delay excitement, so look closely if debugging!
            Pos.x + 3, Pos.y + 20 + 15 - 2
        );
        DrawString(g, "IO p. lines", Pos.x + 3, Pos.y + 20 + 15 + 15 - 4);
    }

    protected void ExciteAsPartner(double ExcitementAdd, DrawObject Sender)
    {
        ExcitementCurrent += LimitExcitement(ExcitementAdd);

        if (DoesActivate(ExcitementCurrent))
        {
            ExcitementTicksRemaining = Strength; // just set to maximal value, also if there are still ticks remaining from any previous excitement
        }
    }

    @Override
    public ArrayList<DrawObject> GetChildDrawObjects()
    {
        ArrayList<DrawObject> ChildDrawObjects = new ArrayList<DrawObject>();

        for (Iterator<SampleAndHoldInputNode> i = InputNodes.iterator(); i.hasNext();)
        {
            SampleAndHoldInputNode o = i.next();
            ChildDrawObjects.add(o);
        }
        for (Iterator<SampleAndHoldOutputNode> i = OutputNodes.iterator(); i.hasNext();)
        {
            SampleAndHoldOutputNode o = i.next();
            ChildDrawObjects.add(o);
        }

        return ChildDrawObjects;
    }

    @Override
    protected void LoseExcitementAndAddExcitementForNextTick()
    {
        // SampleAndHold-specific: actually we don't need
        // ExcitementCurrent and ExcitementAddForNextTick at all,
        // just excite output node as long as excitement ticks
        // remaining!

        ExcitementCurrent = LimitExcitement(ExcitementCurrent);

        ShiftExcitementHistory(ExcitementCurrent);

        // SampleAndHold-specific:
        if (ExcitementTicksRemaining > 0)
        {
            // if (!(IsActivated()))
            {
                ((SampleAndHoldOutputNode) ExcitementPartner).ExciteAsPartner(
                    1.0, // just the default excitement value
                    this
                );

                ExcitementTicksRemaining--;
            }
        }
        // end of specific

        // reset
        ExcitementCurrent = 0.0;
        ExcitementAddForNextTick = 0.0;
    }

    @Override
    public void Move(Point MoveAmount)
    {
        for (Iterator<SampleAndHoldInputNode> i = InputNodes.iterator(); i.hasNext();)
        {
            SampleAndHoldInputNode o = i.next();
            o.Move(MoveAmount);
        }
        for (Iterator<SampleAndHoldOutputNode> i = OutputNodes.iterator(); i.hasNext();)
        {
            SampleAndHoldOutputNode o = i.next();
            o.Move(MoveAmount);
        }

        Pos.x += MoveAmount.x;
        Pos.y += MoveAmount.y;
    }

    @Override
    public void SetHighlighted(boolean IsHighlightedNew)
    {
        IsHighlighted = IsHighlightedNew;

        for (Iterator<SampleAndHoldInputNode> i = InputNodes.iterator(); i.hasNext();)
        {
            SampleAndHoldInputNode o = i.next();
            o.SetHighlighted(IsHighlighted);
        }
        for (Iterator<SampleAndHoldOutputNode> i = OutputNodes.iterator(); i.hasNext();)
        {
            SampleAndHoldOutputNode o = i.next();
            o.SetHighlighted(IsHighlighted);
        }
    }

    @Override
    public void SetPartOfMultiSelection(boolean IsPartNew)
    {
        IsPartOfMultiSelection = IsPartNew;

        for (Iterator<SampleAndHoldInputNode> i = InputNodes.iterator(); i.hasNext();)
        {
            SampleAndHoldInputNode o = i.next();
            o.SetPartOfMultiSelection(IsPartOfMultiSelection);
        }
        for (Iterator<SampleAndHoldOutputNode> i = OutputNodes.iterator(); i.hasNext();)
        {
            SampleAndHoldOutputNode o = i.next();
            o.SetPartOfMultiSelection(IsPartOfMultiSelection);
        }
    }

    @Override
    public void SetPos(Point PosNew)
    {
        PosNew = AlignOnGrid(PosNew);

        Point MoveAmount = new Point(PosNew.x - Pos.x, PosNew.y - Pos.y);

        Move(MoveAmount);
    }

    public void Toggle()
    {
        Strength++;
        if (Strength > StrengthMax)
            Strength = 0;
    }

    public void ToggleIsNegative()
    {
        Strength += Simulation.TicksPerIOTextAreaLine();
        Strength = ((int) (Strength / Simulation.TicksPerIOTextAreaLine())) * Simulation.TicksPerIOTextAreaLine(); // round down
        if (Strength > StrengthMax)
            Strength = 0;
    }
}
